﻿namespace My.Hydrator.Tests
{
    using System;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using My.Hydrator.Conventions;
    using My.Hydrator.Helpers;

    [TestClass]
    public class HydrationTests
    {
        [TestMethod]
        public void HydratorShouldBeAbleToHydrateAllPersonPropertiesWithDefaultValues()
        {
            var hydrator = new Hydrator();

            for (int i = 0; i < 1000; i++)
            {
                var person = hydrator.Hydrate<Person>();

                Assert.IsTrue(person.Id > 0);
                Assert.IsTrue(person.Height > 0);
                Assert.IsTrue(person.Weight > 0);

                Assert.IsTrue(!string.IsNullOrEmpty(person.FirstName));
                Assert.IsTrue(!string.IsNullOrEmpty(person.LastName));
                Assert.IsTrue(!string.IsNullOrEmpty(person.Phone));

                Assert.IsNotNull(person.PhysicalAddress);
                Assert.IsTrue(!string.IsNullOrEmpty(person.PhysicalAddress.City));
                Assert.IsTrue(!string.IsNullOrEmpty(person.PhysicalAddress.StreetName));
                Assert.IsTrue(person.PhysicalAddress.State > 0);
                Assert.IsTrue(person.PhysicalAddress.StreetNumber > 0);
            }
        }

        [TestMethod]
        public void HydratorShouldBeAbleToHydrateAllPersonPropertiesUsingOverloadedConventions()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x =>
                                   {
                                       x.For<int>().Use(new IntTypeConvention(5, 5));
                                       x.For<string>().Use(new StringTypeConvention(15));
                                   });

            for (int i = 0; i < 1000; i++)
            {
                var person = hydrator.Hydrate<Person>();

                Assert.AreEqual(5, person.Id);
                Assert.AreEqual(5, person.Height);
                Assert.AreEqual(5, person.Weight);

                Assert.IsTrue(!string.IsNullOrEmpty(person.FirstName));
                Assert.AreEqual(15, person.FirstName.Length);

                Assert.IsTrue(!string.IsNullOrEmpty(person.LastName));
                Assert.AreEqual(15, person.LastName.Length);

                Assert.IsTrue(!string.IsNullOrEmpty(person.Phone));
                Assert.AreEqual(11, person.Phone.Length);

                Assert.IsNotNull(person.PhysicalAddress);
                Assert.IsTrue(!string.IsNullOrEmpty(person.PhysicalAddress.City));
                Assert.AreEqual(15, person.PhysicalAddress.City.Length);

                Assert.IsTrue(!string.IsNullOrEmpty(person.PhysicalAddress.StreetName));
                Assert.AreEqual(15, person.PhysicalAddress.StreetName.Length);

                Assert.AreEqual(5, person.PhysicalAddress.State);
                Assert.AreEqual(5, person.PhysicalAddress.StreetNumber);
            }
        }

        [TestMethod]
        public void HydratorShouldBeAbleToHydrateSinglePropertiesWithOverloadedConventions()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x =>
            {
                x.For<int>().Use(new IntTypeConvention(5, 5));
                x.For<string>().Use(new StringTypeConvention(15));
                x.For<Person>().Property(y => y.Id).Use(new IntTypeConvention(9, 9));
                x.For<Person>().Property(y => y.LastName).Use(new StringTypeConvention(5));
            });

            for (int i = 0; i < 1000; i++)
            {
                var person = hydrator.Hydrate<Person>();

                Assert.AreEqual(9, person.Id);
                Assert.AreEqual(5, person.Height);
                Assert.AreEqual(5, person.Weight);

                Assert.IsTrue(!string.IsNullOrEmpty(person.FirstName));
                Assert.AreEqual(15, person.FirstName.Length);

                Assert.IsTrue(!string.IsNullOrEmpty(person.LastName));
                Assert.AreEqual(5, person.LastName.Length);

                Assert.IsTrue(!string.IsNullOrEmpty(person.Phone));
                Assert.AreEqual(11, person.Phone.Length);

                Assert.IsNotNull(person.PhysicalAddress);
                Assert.IsTrue(!string.IsNullOrEmpty(person.PhysicalAddress.City));
                Assert.AreEqual(15, person.PhysicalAddress.City.Length);

                Assert.IsTrue(!string.IsNullOrEmpty(person.PhysicalAddress.StreetName));
                Assert.AreEqual(15, person.PhysicalAddress.StreetName.Length);

                Assert.AreEqual(5, person.PhysicalAddress.State);
                Assert.AreEqual(5, person.PhysicalAddress.StreetNumber);
            }
        }

        [TestMethod]
        public void HydratorShouldBeAbleToHydratePropertiesWithCommonTypeValues()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x =>
            {
                x.For<Person>().Property(y => y.FirstName).Is(CommonType.AmericanName);
                x.For<Person>().Property(y => y.LastName).Is(CommonType.AmericanLastName);
                x.For<Person>().Property(y => y.PhysicalAddress.City).Is(CommonType.AmericanCity);
                x.For<Person>().Property(y => y.PhysicalAddress.State).Is(CommonType.AmericanState);
            });

            for (int i = 0; i < 1000; i++)
            {
                var person = hydrator.Hydrate<Person>();

                Assert.IsTrue(!string.IsNullOrEmpty(person.FirstName));
                Assert.IsTrue(Lookups.AmericanNames.Contains(person.FirstName));

                Assert.IsTrue(!string.IsNullOrEmpty(person.LastName));
                Assert.IsTrue(Lookups.AmericanLastNames.Contains(person.LastName));

                Assert.IsNotNull(person.PhysicalAddress);
                Assert.IsTrue(!string.IsNullOrEmpty(person.PhysicalAddress.City));
                Assert.IsTrue(Lookups.AmericanCities.Contains(person.PhysicalAddress.City));

                Assert.IsTrue(!string.IsNullOrEmpty(Lookups.AmericanStates.ElementAtOrDefault(person.PhysicalAddress.State)));
            }
        }

        [TestMethod]
        [ExpectedException(typeof(NotSupportedException))]
        public void HydratorShouldThrowNotSupportedExceptionWhenTryingToConfigureNotSupportedPropertyTypesWithCommonTypes()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x => x.For<Person>().Property(y => y.PhysicalAddress).Is(CommonType.AmericanCity));
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void HydratorShouldThrowArgumentExceptionWhenTryingToOverloadPropertyWithConventionThatSupportedTypeIsDifferent()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x => x.For<Person>().Property(y => y.Id).Use<StringTypeConvention>());
        }

        [TestMethod]
        public void HydratorShouldNotThrowArgumentExceptionWhenTryingToOverloadPropertyThatTypeIsNullableWithConventionThatSupportsSameButNonNullableType()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x => x.For<Person>().Property(y => y.DateOfBirth).Use<DateTimeTypeConvention>());
        }

        [TestMethod]
        public void HydratorShouldPopulateNullablePropertyUsingConventionThatSupportsSameButNonNullableType()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x => x.For<Person>().Property(y => y.DateOfBirth).Use<DateTimeTypeConvention>());

            for (int i = 0; i < 1000; i++)
            {
                var person = hydrator.Hydrate<Person>();

                Assert.IsNotNull(person.DateOfBirth);
                Assert.IsTrue(person.DateOfBirth >= new DateTime(1900, 1, 1));
            }
        }

        [TestMethod]
        public void HydratorShouldSkipPropertyWhileHydrating()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x =>
                                   {
                                       x.For<Person>().Property(y => y.FirstName).Skip();
                                       x.For<Person>().Property(y => y.Id).Skip();
                                   });

            var person = hydrator.Hydrate<Person>();

            Assert.IsNotNull(person);
            Assert.IsNull(person.FirstName);
            Assert.AreEqual(0, person.Id);
        }

        [TestMethod]
        public void HydratorShouldBeAbleToOverloadDataTypeFormatters()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x => x.For(DataType.PhoneNumber).Use(new PhoneFormatter()));

            var person = hydrator.Hydrate<Person>();

            Assert.AreEqual("123/123/123", person.Phone);
        }

        [TestMethod]
        public void HydratorShouldBeAbleToOverloadCustomDataTypeFormatters()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x => x.For("Custom").Use(new CustomFormatter()));

            var person = hydrator.Hydrate<Person>();

            Assert.AreEqual("CustomFormatter", person.Other);
        }

        [TestMethod]
        public void HydratorShouldBeAbleToHydrateEnumerableTypes()
        {
            var hydrator = new Hydrator();

            var person = hydrator.Hydrate<Person>();

            Assert.AreEqual(10, person.Friends.Count());
        }

        [TestMethod]
        public void HydratorShouldBeAbleToHydrateListTypes()
        {
            var hydrator = new Hydrator();

            var person = hydrator.Hydrate<Person>();

            Assert.AreEqual(10, person.FavoriteNumbers.Count);
        }

        [TestMethod]
        public void HydratorShouldBeAbleToHydrateDictionaryTypes()
        {
            var hydrator = new Hydrator();

            var person = hydrator.Hydrate<Person>();

            Assert.AreEqual(10, person.FriendsFavoriteNumbers.Keys.Count);
        }

        [TestMethod]
        public void HydratorShouldBeAbleToOverrideListConvention()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x => x.ForList<int>().Generate(25));

            var person = hydrator.Hydrate<Person>();

            Assert.AreEqual(25, person.FavoriteNumbers.Count);
        }

        [TestMethod]
        public void HydratorShouldBeAbleToOverrideListConventionAndGenericParameterConvention()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x => x.ForList<int>().Generate(25, new IntTypeConvention(99, 99)));

            var person = hydrator.Hydrate<Person>();

            Assert.AreEqual(25, person.FavoriteNumbers.Count);
            foreach (var number in person.FavoriteNumbers)
            {
                Assert.AreEqual(99, number);
            }
        }

        [TestMethod]
        public void HydratorShouldBeAbleToOverrideDictionaryConventionAndGenericParameterConventions()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x => x.ForDictionary<string, int>().Generate(25, new StringTypeConvention(5), new IntTypeConvention(11, 11)));

            var person = hydrator.Hydrate<Person>();

            Assert.AreEqual(25, person.FriendsFavoriteNumbers.Keys.Count);
            foreach (var key in person.FriendsFavoriteNumbers.Keys)
            {
                Assert.AreEqual(5, key.Length);
                Assert.AreEqual(11, person.FriendsFavoriteNumbers[key]);
            }
        }

        [TestMethod]
        public void HydratorShouldBeAbleToOverrideListConventionForSingleProperty()
        {
            var hydrator = new Hydrator();
            hydrator.Configure(x => x.For<Person>().Property(y => y.PhysicalAddress.Coordinates).Use(new ListTypeConvention(new StringTypeConvention(7), 25)));

            var person = hydrator.Hydrate<Person>();

            Assert.AreEqual(10, person.Friends.Count());
            Assert.AreEqual(25, person.PhysicalAddress.Coordinates.Count);
            foreach (var coordinate in person.PhysicalAddress.Coordinates)
            {
                Assert.AreEqual(7, coordinate.Length);
            }
        }

        [TestMethod]
        public void HydratorShouldNotHydratePropertiesWithSameTypeAsBaseOrPrevious()
        {
            var hydrator = new Hydrator();

            var person = hydrator.Hydrate<Person>();

            Assert.IsNull(person.PhysicalAddress.Person);
        }
    }
}
